<template>
  <div ref="container" style="width: 100vw; height: 100vh"></div>
  <div id="tag"></div>
  <div id="pos">
    <div class="pos-item" v-for="item in ['下雨', '整体', '货架1', '货架2', '货架3', '货架4', '货架5', '货架6', '货架7', '货架8']"
      :key="item" @click="handleClick(item)">
      {{ item }}
    </div>
  </div>
</template>

<script setup>
import { ref, onMounted, onBeforeUnmount } from 'vue'
import * as THREE from 'three'
// 引入 OrbitControls
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader'
import { CSS3DRenderer, CSS3DSprite } from "three/examples/jsm/renderers/CSS3DRenderer.js";
import { GUI } from "three/examples/jsm/libs/lil-gui.module.min.js";
import TWEEN from "@tweenjs/tween.js";

import floorImage from "./floor.jpg";
import groundImage from "./ground.jpg";
import raindropImage from "./raindrop.png";
import lightImage from "./light.png";

const container = ref(null)
let renderer, scene, camera, animationId, controls, css3Renderer, spriteArr = [], gui

onMounted(() => {
  gui = new GUI();//创建GUI对象 
  gui.domElement.style.right = '0px';
  gui.domElement.style.width = '300px';
  gui.close();//关闭菜单

  // 场景
  scene = new THREE.Scene()
  scene.background = new THREE.Color(0x000000)

  //辅助观察的坐标系
  // const axesHelper = new THREE.AxesHelper(100);
  // scene.add(axesHelper);

  const width = window.innerWidth; //宽度
  const height = window.innerHeight; //高度
  // 相机
  camera = new THREE.PerspectiveCamera(30, width / height, 1, 2000)
  camera.position.set(0, 90, 130)
  camera.lookAt(0, 0, 0)

  // 相机GUI
  const cameraFolder = gui.addFolder('相机位置');
  cameraFolder.add(camera.position, 'x', 0, 200).name('相机X坐标');
  cameraFolder.add(camera.position, 'y', 0, 200).name('相机Y坐标');
  cameraFolder.add(camera.position, 'z', 0, 200).name('相机Z坐标');

  // 渲染器
  renderer = new THREE.WebGLRenderer({
    antialias: true,//抗锯齿
    // 设置对数深度缓冲区，优化深度冲突问题
    logarithmicDepthBuffer: true
  })
  renderer.setPixelRatio(window.devicePixelRatio);
  renderer.setSize(width, height)
  renderer.shadowMap.enabled = true
  container.value.appendChild(renderer.domElement)
  renderer.domElement.addEventListener('click', function (event) {
    const px = event.offsetX;
    const py = event.offsetY;
    //屏幕坐标转标准设备坐标
    const x = (px / window.innerWidth) * 2 - 1;
    const y = -(py / window.innerHeight) * 2 + 1;
    const raycaster = new THREE.Raycaster();
    //.setFromCamera()在点击位置生成raycaster的射线ray
    raycaster.setFromCamera(new THREE.Vector2(x, y), camera);
    // 射线交叉计算拾取模型
    const intersects = raycaster.intersectObjects(spriteArr);
    if (intersects.length > 0) {
      intersects[0].object.change();//执行选中sprite绑定的change函数
    }
  })

  // CSS3渲染器
  css3Renderer = new CSS3DRenderer();
  css3Renderer.setSize(width, height);
  // HTML标签<div id="tag"></div>外面父元素叠加到canvas画布上且重合
  css3Renderer.domElement.style.position = 'absolute';
  css3Renderer.domElement.style.top = '0px';//具体值根据canvas画布位置来定
  //设置.pointerEvents=none，解决HTML元素标签对threejs canvas画布鼠标事件的遮挡
  css3Renderer.domElement.style.pointerEvents = 'none';
  container.value.appendChild(css3Renderer.domElement);

  // OrbitControls 控件
  controls = new OrbitControls(camera, renderer.domElement)
  controls.enableDamping = true // 启用阻尼（惯性）
  controls.dampingFactor = 0.05
  controls.enablePan = true // 是否禁用平移（可选）
  // controls.addEventListener('change', function () {
  //   renderer.render(scene, camera); //执行渲染操作
  //   // 浏览器控制台查看相机位置变化
  //   console.log('camera.position', camera.position);
  // });//监听鼠标、键盘事件

  // 灯光
  const ambientLight = new THREE.AmbientLight(0xffffff, 0.7)
  scene.add(ambientLight)
  const dirLight = new THREE.DirectionalLight(0xffffff, 0.7)
  dirLight.position.set(50, 50, 50)
  scene.add(dirLight)

  // 光照强度GUI
  const lightFolder = gui.addFolder('灯光强度');
  lightFolder.add(ambientLight, 'intensity', 0, 1).name('环境光强度');
  lightFolder.add(dirLight, 'intensity', 0, 1).name('平行光强度');
  // 平行光位置GUI
  const dirLightFolder = gui.addFolder('平行光位置');
  dirLightFolder.add(dirLight.position, 'x', 0, 200).name('平行光X坐标');
  dirLightFolder.add(dirLight.position, 'y', 0, 200).name('平行光Y坐标');
  dirLightFolder.add(dirLight.position, 'z', 0, 200).name('平行光Z坐标');

  // 设置产生阴影的光源对象,开启光源阴影的计算功能
  dirLight.castShadow = true
  // 设置三维场景计算阴影的范围
  dirLight.shadow.camera.left = -80;
  dirLight.shadow.camera.right = 80;
  dirLight.shadow.camera.top = 50;
  dirLight.shadow.camera.bottom = -50;
  dirLight.shadow.camera.near = 0.5;
  dirLight.shadow.camera.far = 150;

  // 可视化平行光阴影对应的正投影相机对象
  // const cameraHelper = new THREE.CameraHelper(dirLight.shadow.camera);
  // scene.add(cameraHelper);

  // 地面
  const groundTexLoader = new THREE.TextureLoader();
  const groundTexture = groundTexLoader.load(groundImage);
  // 设置阵列模式
  groundTexture.wrapS = THREE.RepeatWrapping;
  groundTexture.wrapT = THREE.RepeatWrapping;
  // uv两个方向纹理重复数量
  groundTexture.repeat.set(30, 30);//注意选择合适的阵列数量
  groundTexture.colorSpace = THREE.SRGBColorSpace;//设置为SRGB颜色空间

  const groundGeometry = new THREE.PlaneGeometry(250, 250)
  const groundMaterial = new THREE.MeshPhongMaterial({
    side: THREE.DoubleSide,
    map: groundTexture,//map表示材质的颜色贴图属性
  })
  const ground = new THREE.Mesh(groundGeometry, groundMaterial)
  ground.rotation.x = -Math.PI / 2  //旋转负 90 度
  ground.position.y = -0.1
  ground.receiveShadow = true
  scene.add(ground)

  // 仓库地面
  const floorTexLoader = new THREE.TextureLoader();
  const floorTexture = floorTexLoader.load(floorImage);
  // 设置阵列模式
  floorTexture.wrapS = THREE.RepeatWrapping;
  floorTexture.wrapT = THREE.RepeatWrapping;
  // uv两个方向纹理重复数量
  floorTexture.repeat.set(30, 30);//注意选择合适的阵列数量
  floorTexture.colorSpace = THREE.SRGBColorSpace;//设置为SRGB颜色空间

  const floorGeometry = new THREE.PlaneGeometry(100, 80)
  const floorMaterial = new THREE.MeshPhongMaterial({
    // color: 0xffffff,
    side: THREE.DoubleSide,
    map: floorTexture,//map表示材质的颜色贴图属性
    // transparent: true,
    // opacity: 0.95
  })
  const floor = new THREE.Mesh(floorGeometry, floorMaterial)
  floor.rotation.x = -Math.PI / 2  //旋转负 90 度
  floor.receiveShadow = true
  scene.add(floor)

  // 墙体参数
  const wallHeight = 10 //高
  const wallThickness = 0.5
  const wallMaterial = new THREE.MeshPhongMaterial({
    color: 0xffffff,
    // transparent: true,
    // opacity: 0.5
  })
  // 四面墙
  for (let z of [-40, 40]) {
    const wallGeometry = new THREE.BoxGeometry(100, wallHeight, wallThickness)
    const wall = new THREE.Mesh(wallGeometry, wallMaterial)
    wall.position.set(0, wallHeight / 2, z)
    wall.castShadow = true
    wall.receiveShadow = true
    scene.add(wall)
  }
  for (let x of [-50, 50]) {
    const wallGeometry = new THREE.BoxGeometry(wallThickness, wallHeight, 80)
    const wall = new THREE.Mesh(wallGeometry, wallMaterial)
    wall.position.set(x, wallHeight / 2, 0)
    wall.castShadow = true
    wall.receiveShadow = true
    scene.add(wall)
  }

  // 添加货架
  function createShelf({
    name = "",
    width = 0.8,
    length = 5,
    height = 5,
    layers = 8,
    layerThickness = 0.05,
    color = 0x888888,
    position = { x: 0, y: 0, z: 0 }
  } = {}) {
    const group = new THREE.Group()
    group.name = name
    const layerGap = (height - layers * layerThickness) / (layers - 1)
    // 层板
    for (let i = 0; i < layers; i++) {
      const y = (layerThickness / 2) + i * (layerThickness + layerGap)
      const boardGeometry = new THREE.BoxGeometry(length, layerThickness, width)
      const boardMaterial = new THREE.MeshPhongMaterial({ color })
      const board = new THREE.Mesh(boardGeometry, boardMaterial)
      board.castShadow = true;
      board.receiveShadow = true;
      board.position.set(0, y, 0)
      group.add(board)

      // 在每层上放箱子
      // 箱子尺寸：0.4m x 0.3m x 0.4m（长*高*宽），间隔0.2m
      const boxLength = 0.4
      const boxHeight = 0.3
      const boxWidth = 0.4
      const boxGap = 0.2 // 间隔20cm
      const boxColor = 0x3399ff
      // 箱子沿长度和宽度方向排满
      const countX = Math.floor((length + boxGap) / (boxLength + boxGap))
      const countZ = Math.floor((width + boxGap) / (boxWidth + boxGap))
      const totalX = countX * boxLength + (countX - 1) * boxGap
      const totalZ = countZ * boxWidth + (countZ - 1) * boxGap
      const startX = -totalX / 2 + boxLength / 2
      const startZ = -totalZ / 2 + boxWidth / 2
      const boxY = y + layerThickness / 2 + boxHeight / 2

      for (let xi = 0; xi < countX; xi++) {
        for (let zi = 0; zi < countZ; zi++) {
          const boxGeometry = new THREE.BoxGeometry(boxLength, boxHeight, boxWidth)
          const boxMaterial = new THREE.MeshPhongMaterial({ color: boxColor })
          const box = new THREE.Mesh(boxGeometry, boxMaterial)
          box.position.set(
            startX + xi * (boxLength + boxGap),
            boxY,
            startZ + zi * (boxWidth + boxGap)
          )
          group.add(box)
        }
      }
    }
    // 四根立柱
    const poleGeometry = new THREE.BoxGeometry(0.05, height, 0.05)
    const poleMaterial = new THREE.MeshPhongMaterial({ color: 0x444444 })
    const polePositions = [
      [-length / 2 + 0.025, height / 2, -width / 2 + 0.025],
      [length / 2 - 0.025, height / 2, -width / 2 + 0.025],
      [-length / 2 + 0.025, height / 2, width / 2 - 0.025],
      [length / 2 - 0.025, height / 2, width / 2 - 0.025]
    ]
    for (const [x, y, z] of polePositions) {
      const pole = new THREE.Mesh(poleGeometry, poleMaterial)
      pole.position.set(x, y, z)
      pole.castShadow = true
      pole.receiveShadow = true;
      group.add(pole)
    }
    
    // 注意是多个标签，需要克隆复制一份
    const div = document.getElementById('tag').cloneNode();
    div.style = 'padding: 5px 8px;border: #00ffff solid 1px;color: #fff;background: rgba(25, 25, 25, 0.5);border-radius: 5px;'
    div.innerHTML = name;//标签数据填写
    // HTML元素转化为threejs的CSS3对象
    const tag = new CSS3DSprite(div);
    // new CSS3DObject(div);之后设置style.pointerEvents 
    div.style.pointerEvents = 'none'; //避免标签遮挡canvas鼠标事件
    group.add(tag);
    tag.scale.set(0.05, 0.05, 0.05);//适当缩放模型标签
    tag.position.y += 6.5;

    // 创建一个精灵模型
    const lightTexture = new THREE.TextureLoader().load(lightImage);
    const spriteMaterial = new THREE.SpriteMaterial({
      // color:0x00ffff,//设置颜色
      map: lightTexture, //设置精灵纹理贴图
      // transparent:true,//默认是true，不用设置
    });
    const sprite = new THREE.Sprite(spriteMaterial);
    sprite.position.set(3, 6, 0);
    sprite.change = function () {
      handleClick(name)
    }
    spriteArr.push(sprite) //存放所有精灵模型
    group.add(sprite);

    group.position.set(position.x, position.y, position.z)
    return group
  }

  // 在仓库中央添加一个货架，总高度为5米
  // const shelf = createShelf({
  //   width: 1, //层板宽度
  //   length: 5, //层板1/2长度 
  //   height: 5, // 货架高度
  //   layers: 8,  //8 层
  //   layerThickness: 0.05, //层板厚度
  //   position: { x: 0, y: 0.1, z: 0 } //模型位置
  // })
  // scene.add(shelf)

  // 创建8个货架并平均分布
  const shelfPositions = [
    { x: -30, z: 15 }, { x: -10, z: 15 }, { x: 10, z: 15 }, { x: 30, z: 15 },
    { x: -30, z: -15 }, { x: -10, z: -15 }, { x: 10, z: -15 }, { x: 30, z: -15 }
  ];

  shelfPositions.forEach((pos, index) => {
    const shelf = createShelf({
      name: `货架${index + 1}`,
      width: 1, // 层板宽度
      length: 5, // 层板长度
      height: 5, // 货架高度
      layers: 8, // 8层
      layerThickness: 0.05, // 层板厚度
      position: { x: pos.x, y: 0.1, z: pos.z } // 模型位置
    });
    scene.add(shelf);
  });

  // 加载货车模型
  const loader = new GLTFLoader()
  let truck
  loader.load('/assets/scene.gltf', (gltf) => {
    truck = gltf.scene
    truck.scale.set(2, 2, 2)
    truck.position.set(-30, 0.1, 0)
    scene.add(truck)
  })

  let direction = 1; // 1 表示向右，-1 表示向左
  const leftLimit = -30;  // 左边界（根据你的货架位置调整）
  const rightLimit = 30; // 右边界（根据你的货架位置调整）
  const speed = 0.1;    // 移动速度

  // 加载雨滴模型
  const raindropTexture = new THREE.TextureLoader().load(raindropImage);
  const spriteMaterial = new THREE.SpriteMaterial({
    map: raindropTexture,
  });
  // 批量创建多个精灵模型，在一个长方体空间上随机分布
  const raindropGroup = new THREE.Group();
  raindropGroup.name = '雨滴'
  raindropGroup.visible = false
  scene.add(raindropGroup);
  for (let i = 0; i < 16000; i++) {
    // 精灵模型共享材质
    const sprite = new THREE.Sprite(spriteMaterial);
    raindropGroup.add(sprite);
    sprite.scale.set(1, 1, 1);
    // 设置精灵模型位置，在长方体空间上上随机分布
    const x = 1000 * (Math.random() - 0.5);
    const y = 600 * Math.random();
    const z = 1000 * (Math.random() - 0.5);
    sprite.position.set(x, y, z)
  }
  const clock = new THREE.Clock();
  function loop() {
    // loop()两次执行时间间隔
    const t = clock.getDelta();
    // loop()每次执行都会更新雨滴的位置，进而产生动画效果
    raindropGroup.children.forEach(sprite => {
      // 雨滴的y坐标每次减t*60
      sprite.position.y -= t * 60;
      if (sprite.position.y < 0) {
        // 如果雨滴落到地面，重置y，从新下落
        sprite.position.y = 600 * Math.random();
      }
    });
    requestAnimationFrame(loop);
  }
  loop();

  // 渲染循环
  function animate() {
    animationId = requestAnimationFrame(animate)
    controls.update() // 更新控制器

    if (truck) {
      truck.position.x += speed * direction;
      if (truck.position.x > rightLimit) {
        direction = -1;
        truck.rotation.y = Math.PI; // 转向
      }
      if (truck.position.x < leftLimit) {
        direction = 1;
        truck.rotation.y = 0; // 转向
      }
    }

    renderer.render(scene, camera)
    css3Renderer.render(scene, camera);
    TWEEN.update();//tween更新
  }
  animate()

  // 响应窗口变化
  window.addEventListener('resize', onWindowResize)
})

function handleClick(item) {
  if (item == '下雨') {
    const raindropGroup = scene.getObjectByName('雨滴');
    raindropGroup.visible = !raindropGroup.visible;
  } else if (item == '整体') {
    const cameraPos0 = new THREE.Vector3(0, 90, 130)
    const target0 = new THREE.Vector3(0, 0, 0);
    createCameraTween(cameraPos0, target0)
  } else {
    const A = scene.getObjectByName(item);
    const pos = new THREE.Vector3();
    //获取三维场景中某个对象世界坐标
    A.getWorldPosition(pos);
    const pos2 = pos.clone()
    pos2.setY(pos2.y + 10)
    pos2.setZ(pos2.z + 20)
    // 向量的x、y、z坐标分别在pos基础上增加30
    // const pos2 = pos.clone().addScalar(5);
    // console.log('pos2', pos2)
    createCameraTween(pos2, pos)
  }
}

// 相机动画函数，从A点飞行到B点，A点表示相机当前所处状态
// pos: 三维向量Vector3，表示动画结束相机位置
// target: 三维向量Vector3，表示相机动画结束lookAt指向的目标观察点
function createCameraTween(endPos, endTarget) {
  new TWEEN.Tween({
    // 不管相机此刻处于什么状态，直接读取当前的位置和目标观察点
    x: camera.position.x,
    y: camera.position.y,
    z: camera.position.z,
    tx: controls.target.x,
    ty: controls.target.y,
    tz: controls.target.z,
  })
    .to({
      // 动画结束相机位置坐标
      x: endPos.x,
      y: endPos.y,
      z: endPos.z,
      // 动画结束相机指向的目标观察点
      tx: endTarget.x,
      ty: endTarget.y + 2.5,
      tz: endTarget.z,
    }, 2000)
    .onUpdate(function (obj) {
      // 动态改变相机位置
      camera.position.set(obj.x, obj.y, obj.z);
      // 动态计算相机视线
      // camera.lookAt(obj.tx, obj.ty, obj.tz);
      controls.target.set(obj.tx, obj.ty, obj.tz);
      controls.update();//内部会执行.lookAt()
    }).
    onComplete((obj) => {
      // console.log('obj', obj);
      // console.log('整个动画最终循环完成触发');
    })
    .start();
}

onBeforeUnmount(() => {
  cancelAnimationFrame(animationId)
  renderer.dispose()
  window.removeEventListener('resize', onWindowResize)
  controls?.dispose()
})

function onWindowResize() {
  camera.aspect = window.innerWidth / window.innerHeight
  camera.updateProjectionMatrix()
  // 渲染设置
  renderer.setSize(window.innerWidth, window.innerHeight)
  css3Renderer.setSize(window.innerWidth, window.innerHeight)
}
</script>

<style>
#pos {
  position: absolute;
  left: 10px;
  top: 50%;
  transform: translateY(-50%);
}

.pos-item {
  padding: 5px 8px;
  border: #00ffff solid 1px;
  color: #fff;
  background: rgba(25, 25, 25, 0.5);
  border-radius: 5px;
  cursor: pointer;
  scale: 0.7;
  text-align: center;
}

.pos-item:not(:last-child) {
  margin-bottom: 10px;
}
</style>